package org.blue.restful.client;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.blue.restful.client.annotation.Body;
import org.blue.restful.client.annotation.GET;
import org.blue.restful.client.annotation.Header;
import org.blue.restful.client.annotation.POST;
import org.blue.restful.client.annotation.Path;
import org.blue.restful.client.annotation.Query;
import org.blue.restful.client.annotation.Result;
import org.blue.restful.client.impl.DefaultDeserializer;
import org.blue.restful.client.impl.DefaultSerializer;

class RestContext {
	final Map<Class<?>, String> availableMethods = new HashMap<Class<?>, String>() {
		private static final long serialVersionUID = 1L;
		{
			put(GET.class, "GET");
			put(POST.class, "POST");
		}
	};
	final Map<Class<?>, Map<String, String>> availableParameters = new HashMap<Class<?>, Map<String, String>>() {
		private static final long serialVersionUID = 1L;
		{
			put(Path.class, null);
			put(Header.class, null);
			put(Query.class, null);
			put(Body.class, null);
		}
	};

	RestServiceFactory factory;
	Object proxy;
	Method method;
	Object[] args;

	String requestMethod = null;
	String requestPath = null;

	RestDeserializer deserializer;
	RestSerializer serializer;

	Class<?> resultClass;

	RestContext(RestServiceFactory factory, Object proxy, Method method, Object[] args) {
		this.factory = factory;
		this.proxy = proxy;
		this.method = method;
		this.args = args;

		//
		// Gets required arguments from our annotations.
		//
		if (!processMethodAnnotation()) {
			return;
		}
		Annotation[][] parametersAnnotations = method.getParameterAnnotations();
		assert (parametersAnnotations.length == args.length);
		for (int i = 0; i < parametersAnnotations.length; i++) {
			Annotation[] parameterAnnotations = parametersAnnotations[i];
			Object arg = args[i];
			processParameterAnnotation(parameterAnnotations, arg);
		}
	}

	boolean isValid() {
		return (requestMethod != null && requestPath != null);
	}

	private void processParameterAnnotation(Annotation[] parameterAnnotations, Object arg) {
		Annotation annotation = findParameterAnnotation(parameterAnnotations);
		if (annotation == null)
			return; // Not a valid parameter, skip it.
		Class<? extends Annotation> clazz = annotation.annotationType();
		try {
			Method valueMethod = clazz.getMethod("value");
			String key = (String) valueMethod.invoke(annotation);
			Map<String, String> args = availableParameters.get(clazz);
			if (args == null) {
				args = new HashMap<String, String>();
				availableParameters.put(clazz, args);
			}
			args.put(key, serializeValue(arg));
		} catch (ReflectiveOperationException e) {
			throw new IllegalStateException("failed to invoke value() of parameter annotation " + annotation);
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}

	}

	private String serializeValue(Object arg) throws IOException {
		return serializer.serialize(arg);
	}

	private <T> T invokeNamedGetter(Class<?> clazz, Object obj, String name) {
		try {
			Method valueMethod = clazz.getMethod(name);
			@SuppressWarnings("unchecked")
			T value = (T) valueMethod.invoke(obj);
			return value;
		} catch (ReflectiveOperationException e) {
			throw new IllegalStateException("failed to invoke named getter: " + name + " on object: " + obj);
		}
	}

	private boolean processMethodAnnotation() {
		Annotation annotation = findMethodAnnotation();
		if (annotation == null)
			return false; // Not an HTTP method, skip it.
		requestPath = invokeNamedGetter(annotation.annotationType(), annotation, "value");
		requestMethod = availableMethods.get(annotation.annotationType());
		if (requestPath == null || requestPath.isEmpty() || requestMethod == null || requestMethod.isEmpty())
			throw new IllegalStateException("incorrect path / method of method annotation " + annotation);

		Class<? extends RestDeserializer> deserializerClass = invokeNamedGetter(annotation.annotationType(), annotation,
				"deserializer");
		Class<? extends RestSerializer> serializerClass = invokeNamedGetter(annotation.annotationType(), annotation,
				"serializer");

		// Result
		resultClass = method.getReturnType();
		Result rst = method.getAnnotation(Result.class);
		Class<? extends Object> targetClass = Object.class;
		if (rst != null) {
			targetClass = rst.target();
		}
		try {
			serializer = (serializerClass == DefaultSerializer.class) ? factory.serializer
					: serializerClass.newInstance();
			deserializer = deserializerClass == DefaultDeserializer.class ? factory.deserializer
					: deserializerClass.newInstance();
		} catch (ReflectiveOperationException e) {
			throw new IllegalStateException("could not create instance of serializer / deserializer.", e);
		}
		if (!targetClass.isAssignableFrom(resultClass)) {
			if (!resultClass.isAssignableFrom(targetClass)) {
				throw new IllegalStateException("return types mismatched.");
			}
			resultClass = targetClass;
		}

		return true;
	}

	private Annotation findMethodAnnotation() {
		for (Annotation annotation : method.getAnnotations()) {
			for (Class<?> annotationClass : availableMethods.keySet()) {
				if (annotation.annotationType().isAssignableFrom(annotationClass)) {
					return annotation;
				}
			}
		}
		return null;
	}

	private Annotation findParameterAnnotation(Annotation[] parameterAnnotations) {
		for (Annotation annotation : parameterAnnotations) {
			for (Class<?> annotationClass : availableParameters.keySet()) {
				if (annotation.annotationType().isAssignableFrom(annotationClass)) {
					return annotation;
				}
			}
		}
		return null;
	}
}